return;
}
- /* Extract complete packet */
+ /*
+ * Extract complete packet
+ *
+ * Yes, this allows an out-of-memory denial of service (DoS)
+ * attack if a number of packets are received with
+ * unreasonable packet sizes, noting that the maximum packet
+ * size is ~4GB. The CTDB protocol was not designed with a
+ * maximum packet size in mind, so CTDB may send very large
+ * valid packets. This means that imposing an arbitrary limit
+ * on incoming packets is not reasonable. Arguments that
+ * header fields, such as magic and/or version, should be
+ * validated before allocating a packet buffer are spurious
+ * from a security perspective because an attacker attempting
+ * DoS can send packets with valid headers. The private
+ * network and ctdbd socket should be secured against
+ * untrusted access, so reports that this possible DoS vector
+ * represents a security issue will be ignored. See the
+ * "Private addresses" section in ctdb(7) for more details.
+ */
data = talloc_memdup(queue->data_pool,
queue->buffer.data + queue->buffer.offset,
pkt_size);
return;
}
+ /*
+ * Allocate buffer for entire packet
+ *
+ * Yes, this allows an out-of-memory denial of service (DoS)
+ * attack if a number of packets are received with
+ * unreasonable packet sizes, noting that the maximum packet
+ * size is ~4GB. The CTDB protocol was not designed with a
+ * maximum packet size in mind, so CTDB may send very large
+ * valid packets. This means that imposing an arbitrary limit
+ * on incoming packets is not reasonable. Arguments that
+ * header fields, such as magic and/or version, should be
+ * validated before allocating a packet buffer are spurious
+ * from a security perspective because an attacker attempting
+ * DoS can send packets with valid headers. The private
+ * network and ctdbd socket should be secured against
+ * untrusted access, so reports that this possible DoS vector
+ * represents a security issue will be ignored. See the
+ * "Private addresses" section in ctdb(7) for more details.
+ */
if (state->use_fixed) {
/* switch to dynamic buffer */
tmp = talloc_array(state, uint8_t, state->total + more);
static int socket_setup(const char *sockpath, bool remove_before_use)
{
struct sockaddr_un addr;
+ const char *t;
+ bool test_mode_enabled = false;
size_t len;
int ret, fd;
return -1;
}
+ t = getenv("CTDB_TEST_MODE");
+ if (t != NULL) {
+ test_mode_enabled = true;
+ }
+
+ if (!test_mode_enabled) {
+ /* Behaviour of fchown(2) is undefined on sockets */
+ ret = chown(sockpath, geteuid(), getegid());
+ if (ret != 0) {
+ D_ERR("Unable to secure (chown) socket '%s'\n",
+ sockpath);
+ close(fd);
+ return -1;
+ }
+ }
+
+ /* Behaviour of fchmod(2) is undefined on sockets */
+ ret = chmod(sockpath, 0700);
+ if (ret != 0) {
+ D_ERR("Unable to secure (chmod) socket '%s'\n", sockpath);
+ close(fd);
+ return -1;
+ }
+
ret = listen(fd, 10);
if (ret != 0) {
D_ERR("socket listen failed - %s\n", sockpath);
services provided by the cluster.
</para>
<para>
- It is strongly recommended that the private addresses are
- configured on a private network that is separate from client
- networks. This is because the CTDB protocol is both
- unauthenticated and unencrypted. If clients share the private
- network then steps need to be taken to stop injection of
- packets to relevant ports on the private addresses. It is
- also likely that CTDB protocol traffic between nodes could
- leak sensitive information if it can be intercepted.
+ It is strongly recommended that the private addresses are
+ configured on a private network that is separate from client
+ networks. This is because <emphasis>the CTDB protocol is both
+ unauthenticated and unencrypted</emphasis>. If clients share
+ the private network then steps need to be taken to stop
+ injection of packets to relevant ports on the private
+ addresses. It is also likely that CTDB protocol traffic
+ between nodes could leak sensitive information if it can be
+ intercepted or manipulated.
+ </para>
+ <para>
+ In summary, the private network should not be accessible by
+ untrusted sources. We can't say "must not" because this can't
+ be enforced by CTDB, so we can only make a strong
+ recommendation. However, for this reason, future bugs in the
+ CTDB protocol used over the private network will be fixed as
+ regular bugs without following the Samba security process.
+ The same is true of local CTDB protocols used for Unix domain
+ socket and other communication.
</para>
<para>
int ctdb_string_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx,
const char **out, size_t *npull)
{
- const char *str;
+ const char *str = NULL;
+ size_t len = 0;
if (buflen > UINT32_MAX) {
return EMSGSIZE;
}
*out = str;
- *npull = ctdb_string_len(&str);
+ /*
+ * Avoid claiming to have consumed more than buflen.
+ * ctdb_string_len() returns buflen + 1 if there is no
+ * NUL-terminator within buflen, so no NUL was actually
+ * consumed (so no +1 needed).
+ */
+ len = ctdb_string_len(&str);
+ *npull = MIN(buflen, len);
return 0;
}
if (ret != 0) {
return ret;
}
- offset += np;
+ /* Always consume the specified number bytes */
+ offset += u32;
*npull = offset;
return 0;
size_t *npull)
{
struct ctdb_statistics_list *val;
+ struct ctdb_statistics dummy = {};
size_t offset = 0, np;
int ret, i;
goto done;
}
+ if ((uint64_t)val->num * ctdb_statistics_len(&dummy) > buflen - offset) {
+ ret = EMSGSIZE;
+ goto fail;
+ }
+
val->stats = talloc_array(val, struct ctdb_statistics, val->num);
if (val->stats == NULL) {
ret = ENOMEM;
struct ctdb_vnn_map *val;
size_t offset = 0, np;
uint32_t i;
+ uint32_t dummy = 0;
int ret;
val = talloc(mem_ctx, struct ctdb_vnn_map);
goto done;
}
+ if ((uint64_t)val->size * ctdb_uint32_len(&dummy) > buflen - offset) {
+ ret = EMSGSIZE;
+ goto fail;
+ }
+
val->map = talloc_array(val, uint32_t, val->size);
if (val->map == NULL) {
ret = ENOMEM;
struct ctdb_dbid_map **out, size_t *npull)
{
struct ctdb_dbid_map *val;
+ struct ctdb_dbid dummy = {};
size_t offset = 0, np;
uint32_t i;
int ret;
goto done;
}
+ if ((uint64_t)val->num * ctdb_dbid_len(&dummy) > buflen - offset) {
+ ret = EMSGSIZE;
+ goto fail;
+ }
+
val->dbs = talloc_array(val, struct ctdb_dbid, val->num);
if (val->dbs == NULL) {
ret = ENOMEM;
struct ctdb_connection_list **out, size_t *npull)
{
struct ctdb_connection_list *val;
+ struct ctdb_connection dummy = {};
size_t offset = 0, np;
uint32_t i;
int ret;
goto done;
}
+ if ((uint64_t)val->num * ctdb_connection_len(&dummy) > buflen - offset) {
+ ret = EMSGSIZE;
+ goto fail;
+ }
+
val->conn = talloc_array(val, struct ctdb_connection, val->num);
if (val->conn == NULL) {
ret = ENOMEM;
struct ctdb_tickle_list **out, size_t *npull)
{
struct ctdb_tickle_list *val;
+ struct ctdb_connection dummy = {};
size_t offset = 0, np;
uint32_t i;
int ret;
goto done;
}
+ if ((uint64_t)val->num * ctdb_connection_len(&dummy) > buflen - offset) {
+ ret = EMSGSIZE;
+ goto fail;
+ }
+
val->conn = talloc_array(val, struct ctdb_connection, val->num);
if (val->conn == NULL) {
ret = ENOMEM;
struct ctdb_public_ip_list **out, size_t *npull)
{
struct ctdb_public_ip_list *val;
+ struct ctdb_public_ip dummy = {};
size_t offset = 0, np;
uint32_t i;
int ret;
goto done;
}
+ if ((uint64_t)val->num * ctdb_public_ip_len(&dummy) > buflen - offset) {
+ ret = EMSGSIZE;
+ goto fail;
+ }
+
val->ip = talloc_array(val, struct ctdb_public_ip, val->num);
if (val->ip == NULL) {
ret = ENOMEM;
struct ctdb_node_map **out, size_t *npull)
{
struct ctdb_node_map *val;
+ struct ctdb_node_and_flags dummy = {};
size_t offset = 0, np;
uint32_t i;
int ret;
goto done;
}
+ if ((uint64_t)val->num * ctdb_node_and_flags_len(&dummy) >
+ buflen - offset) {
+ ret = EMSGSIZE;
+ goto fail;
+ }
+
val->node = talloc_array(val, struct ctdb_node_and_flags, val->num);
if (val->node == NULL) {
ret = ENOMEM;
struct ctdb_script_list **out, size_t *npull)
{
struct ctdb_script_list *val;
+ struct ctdb_script dummy = {};
size_t offset = 0, np;
uint32_t i;
int ret;
goto done;
}
+ if ((uint64_t)val->num_scripts * ctdb_script_len(&dummy)
+ > buflen - offset) {
+ ret = EMSGSIZE;
+ goto fail;
+ }
+
val->script = talloc_array(val, struct ctdb_script, val->num_scripts);
if (val->script == NULL) {
ret = ENOMEM;
struct ctdb_iface_list **out, size_t *npull)
{
struct ctdb_iface_list *val;
+ struct ctdb_iface dummy = {};
size_t offset = 0, np;
uint32_t i;
int ret;
goto done;
}
+ if ((uint64_t)val->num * ctdb_iface_len(&dummy) > buflen - offset) {
+ ret = EMSGSIZE;
+ goto fail;
+ }
+
val->iface = talloc_array(val, struct ctdb_iface, val->num);
if (val->iface == NULL) {
ret = ENOMEM;
val->num = buflen / ctdb_g_lock_len(&lock);
+ /*
+ * No array count pre-check needed because val->num is
+ * calculated from buflen
+ */
val->lock = talloc_array(val, struct ctdb_g_lock, val->num);
if (val->lock == NULL) {
ret = ENOMEM;
void ctdb_request_dmaster(struct ctdb_context *ctdb, struct ctdb_req_header *hdr)
{
struct ctdb_req_dmaster_old *c = (struct ctdb_req_dmaster_old *)hdr;
+ size_t req_dmaster_header_len = offsetof(struct ctdb_req_dmaster_old,
+ data);
+ size_t buf_len = hdr->length;
TDB_DATA key, data, data2;
struct ctdb_ltdb_header header;
struct ctdb_db_context *ctdb_db;
size_t len;
int ret;
+ if (buf_len < req_dmaster_header_len ||
+ c->keylen > buf_len - req_dmaster_header_len ||
+ c->datalen > buf_len - req_dmaster_header_len - c->keylen) {
+ DBG_WARNING("Invalid packet\n");
+ return;
+ }
+
key.dptr = c->data;
key.dsize = c->keylen;
data.dptr = c->data + c->keylen;
void ctdb_request_call(struct ctdb_context *ctdb, struct ctdb_req_header *hdr)
{
struct ctdb_req_call_old *c = (struct ctdb_req_call_old *)hdr;
+ size_t req_call_header_len = offsetof(struct ctdb_req_call_old, data);
+ size_t buf_len = hdr->length;
TDB_DATA data;
struct ctdb_reply_call_old *r;
int ret, len;
struct ctdb_db_context *ctdb_db;
int tmp_count, bucket;
+ if (buf_len < req_call_header_len ||
+ c->keylen > buf_len - req_call_header_len ||
+ c->calldatalen > buf_len - req_call_header_len - c->keylen) {
+ DBG_WARNING("Invalid packet\n");
+ return;
+ }
+
if (ctdb->methods == NULL) {
DEBUG(DEBUG_INFO,(__location__ " Failed ctdb_request_call. Transport is DOWN\n"));
return;
void ctdb_reply_call(struct ctdb_context *ctdb, struct ctdb_req_header *hdr)
{
struct ctdb_reply_call_old *c = (struct ctdb_reply_call_old *)hdr;
+ size_t reply_call_header_len = offsetof(struct ctdb_reply_call_old,
+ data);
+ size_t buf_len = hdr->length;
struct ctdb_call_state *state;
+ if (buf_len < reply_call_header_len ||
+ c->datalen > buf_len - reply_call_header_len) {
+ DBG_WARNING("Invalid packet\n");
+ return;
+ }
+
state = reqid_find(ctdb->idr, hdr->reqid, struct ctdb_call_state);
if (state == NULL) {
DEBUG(DEBUG_ERR, (__location__ " reqid %u not found\n", hdr->reqid));
void ctdb_reply_dmaster(struct ctdb_context *ctdb, struct ctdb_req_header *hdr)
{
struct ctdb_reply_dmaster_old *c = (struct ctdb_reply_dmaster_old *)hdr;
+ size_t reply_dmaster_header_len = offsetof(struct ctdb_reply_dmaster_old,
+ data);
+ size_t buf_len = hdr->length;
struct ctdb_db_context *ctdb_db;
TDB_DATA key, data;
uint32_t record_flags = 0;
size_t len;
int ret;
+ if (buf_len < reply_dmaster_header_len ||
+ c->keylen > buf_len - reply_dmaster_header_len ||
+ c->datalen > buf_len - reply_dmaster_header_len - c->keylen) {
+ DBG_WARNING("Invalid packet\n");
+ return;
+ }
+
ctdb_db = find_ctdb_db(ctdb, c->db_id);
if (ctdb_db == NULL) {
DEBUG(DEBUG_ERR,("Unknown db_id 0x%x in ctdb_reply_dmaster\n", c->db_id));
struct ctdb_req_header *hdr)
{
struct ctdb_req_message_old *c = (struct ctdb_req_message_old *)hdr;
+ size_t req_message_header_len = offsetof(struct ctdb_req_message_old,
+ data);
+ size_t buf_len = hdr->length;
TDB_DATA data;
+ if (buf_len < req_message_header_len ||
+ c->datalen > buf_len - req_message_header_len) {
+ DBG_WARNING("Invalid packet\n");
+ return;
+ }
+
data.dsize = c->datalen;
data.dptr = talloc_memdup(c, &c->data[0], c->datalen);
if (data.dptr == NULL) {
void ctdb_request_control(struct ctdb_context *ctdb, struct ctdb_req_header *hdr)
{
struct ctdb_req_control_old *c = (struct ctdb_req_control_old *)hdr;
+ size_t req_control_header_len = offsetof(struct ctdb_req_control_old,
+ data);
+ size_t buf_len = hdr->length;
TDB_DATA data, *outdata;
int32_t status;
bool async_reply = false;
const char *errormsg = NULL;
+ if (buf_len < req_control_header_len ||
+ c->datalen > buf_len - req_control_header_len) {
+ DBG_WARNING("Invalid packet\n");
+ return;
+ }
+
data.dptr = &c->data[0];
data.dsize = c->datalen;
void ctdb_reply_control(struct ctdb_context *ctdb, struct ctdb_req_header *hdr)
{
struct ctdb_reply_control_old *c = (struct ctdb_reply_control_old *)hdr;
+ size_t reply_control_header_len = offsetof(struct ctdb_reply_control_old,
+ data);
+ size_t buf_len = hdr->length;
TDB_DATA data;
struct ctdb_control_state *state;
const char *errormsg = NULL;
+ if (buf_len < reply_control_header_len ||
+ c->datalen > buf_len - reply_control_header_len ||
+ c->errorlen > buf_len - reply_control_header_len - c->datalen) {
+ DBG_WARNING("Invalid packet\n");
+ return;
+ }
+
state = reqid_find(ctdb->idr, hdr->reqid, struct ctdb_control_state);
if (state == NULL) {
DEBUG(DEBUG_ERR,("pnn %u Invalid reqid %u in ctdb_reply_control\n",
struct ctdb_db_context *db;
struct ctdb_node *node = ctdb->nodes[ctdb->pnn];
struct ctdb_client *client = NULL;
+ char *t = NULL;
uint32_t opcode;
+ t = memchr(indata.dptr, '\0', indata.dsize);
+ if (t == NULL) {
+ DBG_ERR("Invalid packet\n");
+ return -1;
+ }
+
if (ctdb->tunable.allow_client_db_attach == 0) {
DEBUG(DEBUG_ERR, ("DB Attach to database %s denied by tunable "
"AllowClientDBAccess == 0\n", db_name));
struct ctdb_marshall_buffer *m = (struct ctdb_marshall_buffer *)recdata.dptr;
struct ctdb_db_context *ctdb_db;
+ if (recdata.dsize < offsetof(struct ctdb_marshall_buffer, data)) {
+ DBG_ERR("Invalid packet\n");
+ return -1;
+ }
+ if (m->count >
+ recdata.dsize - offsetof(struct ctdb_marshall_buffer, data)) {
+ DBG_ERR("Invalid packet\n");
+ return -1;
+ }
+
if (ctdb->recovery_mode != CTDB_RECOVERY_NORMAL) {
DEBUG(DEBUG_INFO,("rejecting ctdb_control_trans3_commit when recovery active\n"));
return -1;
#include "ctdb_private.h"
#include "ctdb_client.h"
+#include "protocol/protocol_private.h"
+
#include "common/system.h"
#include "common/common.h"
#include "common/logging.h"
int
ctdb_control_setvnnmap(struct ctdb_context *ctdb, uint32_t opcode, TDB_DATA indata, TDB_DATA *outdata)
{
- struct ctdb_vnn_map_wire *map = (struct ctdb_vnn_map_wire *)indata.dptr;
+ struct ctdb_vnn_map *new = NULL;
+ size_t npull = 0;
+ int ret = 0;
if (ctdb->recovery_mode != CTDB_RECOVERY_ACTIVE) {
DEBUG(DEBUG_ERR, ("Attempt to set vnnmap when not in recovery\n"));
return -1;
}
- talloc_free(ctdb->vnn_map);
-
- ctdb->vnn_map = talloc(ctdb, struct ctdb_vnn_map);
- CTDB_NO_MEMORY(ctdb, ctdb->vnn_map);
-
- ctdb->vnn_map->generation = map->generation;
- ctdb->vnn_map->size = map->size;
- ctdb->vnn_map->map = talloc_array(ctdb->vnn_map, uint32_t, map->size);
- CTDB_NO_MEMORY(ctdb, ctdb->vnn_map->map);
+ ret = ctdb_vnn_map_pull(indata.dptr, indata.dsize, ctdb, &new, &npull);
+ if (ret != 0) {
+ switch (ret) {
+ case ENOMEM:
+ DBG_ERR("Memory allocation error\n");
+ break;
+ case EMSGSIZE:
+ DBG_ERR("Invalid packet\n");
+ break;
+ default:
+ DBG_ERR("Unexpected error (%d)\n", ret);
+ }
+ return -1;
+ }
- memcpy(ctdb->vnn_map->map, map->map, sizeof(uint32_t)*map->size);
+ talloc_free(ctdb->vnn_map);
+ ctdb->vnn_map = new;
return 0;
}
return -1;
}
+ if (reply->count >
+ indata.dsize - offsetof(struct ctdb_marshall_buffer, data)) {
+ DBG_ERR("Invalid packet\n");
+ return -1;
+ }
+
ctdb_db = find_ctdb_db(ctdb, reply->db_id);
if (!ctdb_db) {
DEBUG(DEBUG_ERR,(__location__ " Unknown db 0x%08x\n", reply->db_id));
int32_t ctdb_control_traverse_data(struct ctdb_context *ctdb, TDB_DATA data, TDB_DATA *outdata)
{
struct ctdb_rec_data_old *d = (struct ctdb_rec_data_old *)data.dptr;
+ size_t rec_data_header_len = offsetof(struct ctdb_rec_data_old, data);
struct ctdb_traverse_all_handle *state;
TDB_DATA key;
ctdb_traverse_fn_t callback;
void *private_data;
- if (data.dsize < sizeof(uint32_t) || data.dsize != d->length) {
- DEBUG(DEBUG_ERR,("Bad record size in ctdb_control_traverse_data\n"));
+ if (data.dsize < rec_data_header_len ||
+ data.dsize != d->length ||
+ d->keylen > d->length - rec_data_header_len ||
+ d->datalen > d->length - rec_data_header_len - d->keylen) {
+ DBG_ERR("Invalid packet\n");
return -1;
}
struct childwrite_handle *handle;
struct ctdb_marshall_buffer *m = (struct ctdb_marshall_buffer *)recdata.dptr;
+ if (recdata.dsize < offsetof(struct ctdb_marshall_buffer, data)) {
+ DBG_ERR("Invalid packet\n");
+ return -1;
+ }
+ if (m->count >
+ recdata.dsize - offsetof(struct ctdb_marshall_buffer, data)) {
+ DBG_ERR("Invalid packet\n");
+ return -1;
+ }
+
if (ctdb->recovery_mode != CTDB_RECOVERY_NORMAL) {
DEBUG(DEBUG_INFO,("rejecting ctdb_control_update_record when recovery active\n"));
return -1;
} dns_rdata;
typedef [flag(LIBNDR_PRINT_ARRAY_HEX|NDR_NOALIGN),nopush,nopull] struct {
+ /*
+ * This is the start offset of this
+ * dns_res_rec relative to the start of
+ * dns_name_packet buffer.
+ */
+ [ignore] uint32 start_ndr_offset;
dns_string name;
dns_qtype rr_type;
dns_qclass rr_class;
ndr_set_flags(&ndr->flags, LIBNDR_PRINT_ARRAY_HEX |
LIBNDR_FLAG_NOALIGN);
if (ndr_flags & NDR_SCALARS) {
+ /*
+ * Remember the start offset in order
+ * to know how to truncate before the
+ * last dns_res_rec in order to do
+ * the correct TSIG calculation.
+ *
+ * Note that we just set LIBNDR_FLAG_NOALIGN
+ * above, so ndr_pull_align is a no-op.
+ */
+ r->start_ndr_offset = ndr->offset;
+
NDR_CHECK(ndr_pull_align(ndr, 4));
NDR_CHECK(ndr_pull_dns_string(ndr, NDR_SCALARS, &r->name));
NDR_CHECK(ndr_pull_dns_qtype(ndr, NDR_SCALARS, &r->rr_type));
struct dns_res_rec *old,
struct dns_res_rec *new_rec)
{
+ new_rec->start_ndr_offset = 0;
+
new_rec->name = talloc_strdup(mem_ctx, old->name);
W_ERROR_HAVE_NO_MEMORY(new_rec->name);
NTSTATUS status;
enum ndr_err_code ndr_err;
uint16_t i, arcount = 0;
- DATA_BLOB tsig_blob, fake_tsig_blob, sig;
+ DATA_BLOB fake_tsig_blob, sig;
uint8_t *buffer = NULL;
size_t buffer_len = 0, packet_len = 0;
struct dns_server_tkey *tkey = NULL;
state->sign = true;
DBG_DEBUG("Got TSIG\n");
+ /*
+ * We need to keep the input packet exactly like we got it,
+ * but we need to cut off the tsig record.
+ *
+ * DNS packets can not be larger than UINT16_MAX,
+ * the size of the UDP payload is uint16_t and
+ * there's a uint16_t length header for TCP.
+ *
+ * And the start offset of the last
+ * additional record can't be larger than
+ * the whole packet.
+ */
+ packet_len = packet->additional[i].start_ndr_offset;
+ SMB_ASSERT(in->length <= UINT16_MAX);
+ SMB_ASSERT(in->length > packet_len);
+
state->tsig = talloc_zero(state->mem_ctx, struct dns_res_rec);
if (state->tsig == NULL) {
return WERR_NOT_ENOUGH_MEMORY;
check_rec->other_size = 0;
check_rec->other_data = NULL;
- ndr_err = ndr_push_struct_blob(&tsig_blob, mem_ctx, state->tsig,
- (ndr_push_flags_fn_t)ndr_push_dns_res_rec);
- if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
- DEBUG(1, ("Failed to push packet: %s!\n",
- ndr_errstr(ndr_err)));
- return DNS_ERR(SERVER_FAILURE);
- }
-
ndr_err = ndr_push_struct_blob(&fake_tsig_blob, mem_ctx, check_rec,
(ndr_push_flags_fn_t)ndr_push_dns_fake_tsig_rec);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
return DNS_ERR(SERVER_FAILURE);
}
- /* we need to work some magic here. we need to keep the input packet
- * exactly like we got it, but we need to cut off the tsig record */
- packet_len = in->length - tsig_blob.length;
+ /*
+ * We already asserted packet_len < UINT16_MAX
+ * above, and struct ndr_push has alloc_size and
+ * offset as uint32_t, so it's really unlikely
+ * to overflow buffer_len. For SIZE_MAX == UINT32_MAX,
+ * the following check might be important, but
+ * just lets do it always.
+ */
+ if (fake_tsig_blob.length > (SIZE_MAX - UINT16_MAX)) {
+ DBG_WARNING("fake_tsig_blob.length=%zu too large!\n",
+ fake_tsig_blob.length);
+ return DNS_ERR(SERVER_FAILURE);
+ }
buffer_len = packet_len + fake_tsig_blob.length;
buffer = talloc_zero_array(mem_ctx, uint8_t, buffer_len);
if (buffer == NULL) {
return WERR_OK;
}
-static NTSTATUS create_tkey(struct dns_server *dns,
+static NTSTATUS create_tkey(TALLOC_CTX *mem_ctx,
+ struct dns_server *dns,
const char* name,
const char* algorithm,
const struct tsocket_address *remote_address,
struct dns_server_tkey **tkey)
{
NTSTATUS status;
- struct dns_server_tkey_store *store = dns->tkeys;
struct dns_server_tkey *k = NULL;
if (strcmp(algorithm, "gss-tsig") == 0) {
return NT_STATUS_ACCESS_DENIED;
}
- k = talloc_zero(store, struct dns_server_tkey);
+ k = talloc_zero(mem_ctx, struct dns_server_tkey);
if (k == NULL) {
- return NT_STATUS_NO_MEMORY;
+ goto nomem;
}
k->name = talloc_strdup(k, name);
if (k->name == NULL) {
- return NT_STATUS_NO_MEMORY;
+ goto nomem;
}
k->algorithm = talloc_strdup(k, algorithm);
if (k->algorithm == NULL) {
- return NT_STATUS_NO_MEMORY;
+ goto nomem;
}
/*
&k->gensec);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(1, ("Failed to start GENSEC server code: %s\n", nt_errstr(status)));
- *tkey = NULL;
- return status;
+ goto fail;
}
gensec_want_feature(k->gensec, GENSEC_FEATURE_SIGN);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(1, ("Failed to set remote address into GENSEC: %s\n",
nt_errstr(status)));
- *tkey = NULL;
- return status;
+ goto fail;
}
status = gensec_set_local_address(k->gensec,
if (!NT_STATUS_IS_OK(status)) {
DEBUG(1, ("Failed to set local address into GENSEC: %s\n",
nt_errstr(status)));
- *tkey = NULL;
- return status;
+ goto fail;
}
status = gensec_start_mech_by_oid(k->gensec, GENSEC_OID_SPNEGO);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(1, ("Failed to start GENSEC server code: %s\n",
nt_errstr(status)));
- *tkey = NULL;
- return status;
+ goto fail;
}
+ *tkey = k;
+ return NT_STATUS_OK;
+
+nomem:
+ status = NT_STATUS_NO_MEMORY;
+fail:
+ TALLOC_FREE(k);
+ *tkey = NULL;
+ return status;
+}
+
+static void add_tkey(struct dns_server_tkey_store *store,
+ struct dns_server_tkey **tkey)
+{
TALLOC_FREE(store->tkeys[store->next_idx]);
- store->tkeys[store->next_idx] = k;
+ store->tkeys[store->next_idx] = talloc_move(store->tkeys, tkey);
+
(store->next_idx)++;
store->next_idx %= store->size;
-
- *tkey = k;
- return NT_STATUS_OK;
}
static NTSTATUS accept_gss_ticket(TALLOC_CTX *mem_ctx,
struct dns_server_tkey *tkey;
DATA_BLOB key;
DATA_BLOB reply;
+ bool created = false;
tkey = dns_find_tkey(dns->tkeys, in->questions[0].name);
if (tkey != NULL && tkey->complete) {
}
if (tkey == NULL) {
- status = create_tkey(dns, in->questions[0].name,
- in_tkey->rdata.tkey_record.algorithm,
- state->remote_address,
- state->local_address,
- &tkey);
+ status = create_tkey(
+ mem_ctx,
+ dns,
+ in->questions[0].name,
+ in_tkey->rdata.tkey_record.algorithm,
+ state->remote_address,
+ state->local_address,
+ &tkey);
if (!NT_STATUS_IS_OK(status)) {
ret_tkey->rdata.tkey_record.error = DNS_RCODE_BADKEY;
return ntstatus_to_werror(status);
}
+ created = true;
}
key.data = in_tkey->rdata.tkey_record.key_data;
if (state->key_name == NULL) {
return WERR_NOT_ENOUGH_MEMORY;
}
+ if (created) {
+ add_tkey(dns->tkeys, &tkey);
+ }
} else {
DEBUG(1, ("GSS key negotiation returned %s\n", nt_errstr(status)));
ret_tkey->rdata.tkey_record.error = DNS_RCODE_BADKEY;
DBG_ERR("Unable to create ldb attributes JSON audit message\n");
return attributes;
}
+
+/*
+ * @brief Generate a human readable string, detailing attributes in a message
+ *
+ * For modify operations each attribute is prefixed with the action.
+ * Normal values are enclosed in []
+ * Base64 values are enclosed in {}
+ * Truncated values are indicated by three trailing dots "..."
+ *
+ * @param[in] ldb the ldb_context
+ * @param[out] buffer the attributes will be appended to the buffer.
+ * assumed to have been allocated via talloc.
+ * @param[in] operation the operation type
+ * @param[in] message the message to process
+ *
+ * @return a pointer to buffer
+ *
+ */
+char *dsdb_audit_log_attributes(
+ struct ldb_context *ldb,
+ char *buffer,
+ enum ldb_request_type operation,
+ const struct ldb_message *message)
+{
+ size_t i, j;
+ for (i=0;i<message->num_elements;i++) {
+ if (i > 0) {
+ buffer = talloc_asprintf_append_buffer(buffer, " ");
+ }
+
+ if (message->elements[i].name == NULL) {
+ ldb_debug(
+ ldb,
+ LDB_DEBUG_ERROR,
+ "Error: Invalid element name (NULL) at "
+ "position %zu", i);
+ return NULL;
+ }
+
+ if (operation == LDB_MODIFY) {
+ const char *action =NULL;
+ action = dsdb_audit_get_modification_action(
+ message->elements[i].flags);
+ buffer = talloc_asprintf_append_buffer(
+ buffer,
+ "%s: %s ",
+ action,
+ message->elements[i].name);
+ } else {
+ buffer = talloc_asprintf_append_buffer(
+ buffer,
+ "%s ",
+ message->elements[i].name);
+ }
+
+ if (dsdb_audit_redact_attribute(message->elements[i].name)) {
+ /*
+ * Do not log the value of any secret or password
+ * attributes
+ */
+ buffer = talloc_asprintf_append_buffer(
+ buffer,
+ "[REDACTED SECRET ATTRIBUTE]");
+ continue;
+ }
+
+ for (j=0;j<message->elements[i].num_values;j++) {
+ struct ldb_val v;
+ bool use_b64_encode = false;
+ size_t length;
+ if (j > 0) {
+ buffer = talloc_asprintf_append_buffer(
+ buffer,
+ " ");
+ }
+
+ v = message->elements[i].values[j];
+ length = MIN(MAX_LENGTH, v.length);
+ use_b64_encode = ldb_should_b64_encode(ldb, &v);
+ if (use_b64_encode) {
+ const char *encoded = ldb_base64_encode(
+ buffer,
+ (char *)v.data,
+ length);
+ buffer = talloc_asprintf_append_buffer(
+ buffer,
+ "{%s%s}",
+ encoded,
+ (v.length > MAX_LENGTH ? "..." : ""));
+ } else {
+ buffer = talloc_asprintf_append_buffer(
+ buffer,
+ "[%*.*s%s]",
+ (int)length,
+ (int)length,
+ (char *)v.data,
+ (v.length > MAX_LENGTH ? "..." : ""));
+ }
+ }
+ }
+ return buffer;
+}
+
+/*
+ * @brief generate a human readable log entry detailing an ldb operation.
+ *
+ * Generate a human readable log entry detailing an ldb operation.
+ *
+ * @param[in] mem_ctx the talloc context owning the returned string.
+ * @param[in] module the ldb module
+ * @param[in] request the request
+ * @param[in] reply the result of the operation
+ *
+ * @return the log entry.
+ *
+ */
+char *dsdb_audit_operation_human_readable(
+ TALLOC_CTX *mem_ctx,
+ struct ldb_module *module,
+ const struct ldb_request *request,
+ const struct ldb_reply *reply)
+{
+ struct ldb_context *ldb = NULL;
+ const char *remote_host = NULL;
+ const struct tsocket_address *remote = NULL;
+ const struct dom_sid *sid = NULL;
+ struct dom_sid_buf user_sid;
+ const char *timestamp = NULL;
+ const char *op_name = NULL;
+ char *log_entry = NULL;
+ const char *dn = NULL;
+ const char *new_dn = NULL;
+ const struct ldb_message *message = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_module_get_ctx(module);
+
+ remote_host = dsdb_audit_get_remote_host(ldb, ctx);
+ remote = dsdb_audit_get_remote_address(ldb);
+ if (remote != NULL && dsdb_audit_is_system_session(module)) {
+ sid = dsdb_audit_get_actual_sid(ldb);
+ } else {
+ sid = dsdb_audit_get_user_sid(module);
+ }
+ timestamp = audit_get_timestamp(ctx);
+ op_name = dsdb_audit_get_operation_name(request);
+ dn = dsdb_audit_get_primary_dn(request);
+ new_dn = dsdb_audit_get_secondary_dn(request);
+
+ message = dsdb_audit_get_message(request);
+
+ log_entry = talloc_asprintf(
+ mem_ctx,
+ "[%s] at [%s] status [%s] "
+ "remote host [%s] SID [%s] DN [%s]",
+ op_name,
+ timestamp,
+ ldb_strerror(reply->error),
+ remote_host,
+ dom_sid_str_buf(sid, &user_sid),
+ dn);
+ if (new_dn != NULL) {
+ log_entry = talloc_asprintf_append_buffer(
+ log_entry,
+ " New DN [%s]",
+ new_dn);
+ }
+ if (message != NULL) {
+ log_entry = talloc_asprintf_append_buffer(log_entry,
+ " attributes [");
+ log_entry = dsdb_audit_log_attributes(ldb,
+ log_entry,
+ request->operation,
+ message);
+ log_entry = talloc_asprintf_append_buffer(log_entry, "]");
+ }
+ TALLOC_FREE(ctx);
+ return log_entry;
+}
#include "dsdb/samdb/samdb.h"
#include "version.h"
#include "dsdb/samdb/ldb_modules/util.h"
+#include "dsdb/samdb/ldb_modules/audit_util_proto.h"
#include "libcli/security/security.h"
#include "librpc/ndr/libndr.h"
#include "auth/auth.h"
return LDB_SUCCESS;
}
-/* Ensure that anonymous users are not allowed to make anything other than rootDSE search operations */
-
+/*
+ * Ensure that anonymous users are not allowed to make anything other than
+ * rootDSE search operations and special dns like @MODULES are not allowed
+ * over an untrusted connection.
+ */
static int rootdse_filter_operations(struct ldb_module *module, struct ldb_request *req)
{
struct auth_session_info *session_info;
struct rootdse_private_data *priv = talloc_get_type(ldb_module_get_private(module), struct rootdse_private_data);
bool is_untrusted = ldb_req_is_untrusted(req);
bool is_anonymous = true;
+ struct ldb_dn *dn = NULL;
+ struct ldb_dn *dn2 = NULL;
+
if (is_untrusted == false) {
return LDB_SUCCESS;
}
+ switch (req->operation) {
+ case LDB_SEARCH:
+ dn = req->op.search.base;
+ break;
+ case LDB_ADD:
+ dn = req->op.add.message->dn;
+ break;
+ case LDB_MODIFY:
+ dn = req->op.mod.message->dn;
+ break;
+ case LDB_DELETE:
+ dn = req->op.del.dn;
+ break;
+ case LDB_RENAME:
+ dn = req->op.rename.olddn;
+ dn2 = req->op.rename.newdn;
+ break;
+ case LDB_EXTENDED:
+ break;
+ case LDB_REQ_REGISTER_CONTROL:
+ case LDB_REQ_REGISTER_PARTITION:
+ ldb_set_errstring(ldb_module_get_ctx(module), "Invalid OP");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (ldb_dn_is_special(dn)) {
+ struct ldb_reply reply = { .error = LDB_ERR_OPERATIONS_ERROR, };
+
+ D_ERR("CVE-2026-58221-ATTACK: %s\n",
+ dsdb_audit_operation_human_readable(req, module, req, &reply));
+
+ ldb_set_errstring(ldb_module_get_ctx(module), "Invalid DN");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ if (ldb_dn_is_special(dn2)) {
+ struct ldb_reply reply = { .error = LDB_ERR_OPERATIONS_ERROR, };
+
+ D_ERR("CVE-2026-58221-ATTACK: %s\n",
+ dsdb_audit_operation_human_readable(req, module, req, &reply));
+
+ ldb_set_errstring(ldb_module_get_ctx(module), "Invalid DN");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
session_info = (struct auth_session_info *)ldb_get_opaque(
ldb_module_get_ctx(module),
DSDB_SESSION_INFO);
init_function='ldb_rootdse_module_init',
module_init_name='ldb_init_module',
internal_module=False,
- deps='talloc samdb MESSAGING samba-security DSDB_MODULE_HELPERS RPC_NDR_IRPC'
+ deps='talloc samdb MESSAGING samba-security DSDB_MODULE_HELPERS DSDB_MODULE_HELPERS_AUDIT RPC_NDR_IRPC'
)
ap_req_blob = data_blob_const(&request->data[HEADER_LEN], ap_req_len);
- enc_data_len = len - ap_req_len;
+ enc_data_len = len - (ap_req_len + HEADER_LEN);
enc_data_blob = data_blob_const(&request->data[HEADER_LEN + ap_req_len],
enc_data_len);
#include "samba/service_stream.h"
#include "dsdb/gmsa/util.h"
#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/util.h"
#include <ldb_errors.h>
#include <ldb_module.h>
#include "ldb_wrap.h"
struct ldb_dn *dn;
const char *attrs[1];
const char *errstr = NULL;
+ const char *value = NULL;
const char *filter = NULL;
int result = LDAP_SUCCESS;
int ldb_ret;
dn = ldb_dn_new(local_ctx, samdb, req->dn);
NT_STATUS_HAVE_NO_MEMORY(dn);
+ compare_r = ldapsrv_init_reply(call, LDAP_TAG_CompareResponse);
+ NT_STATUS_HAVE_NO_MEMORY(compare_r);
+
DBG_DEBUG("dn: [%s]\n", req->dn);
- filter = talloc_asprintf(local_ctx, "(%s=%*s)", req->attribute,
- (int)req->value.length, req->value.data);
+
+ if (!ldb_valid_attr_name(req->attribute)) {
+ result = LDAP_INVALID_ATTRIBUTE_SYNTAX;
+ errstr = "Invalid Compare attribute name";
+ goto reply;
+ }
+ value = ldb_binary_encode(local_ctx, req->value);
+ NT_STATUS_HAVE_NO_MEMORY(value);
+
+ filter = talloc_asprintf(local_ctx, "%s=%s", req->attribute, value);
NT_STATUS_HAVE_NO_MEMORY(filter);
DBG_DEBUG("attribute: [%s]\n", filter);
attrs[0] = NULL;
- compare_r = ldapsrv_init_reply(call, LDAP_TAG_CompareResponse);
- NT_STATUS_HAVE_NO_MEMORY(compare_r);
-
if (result == LDAP_SUCCESS) {
- ldb_ret = ldb_search(samdb, local_ctx, &res,
- dn, LDB_SCOPE_BASE, attrs, "%s", filter);
+ ldb_ret = dsdb_search(samdb, local_ctx, &res,
+ dn, LDB_SCOPE_BASE, attrs,
+ DSDB_MARK_REQ_UNTRUSTED,
+ "%s", filter);
if (ldb_ret != LDB_SUCCESS) {
result = map_ldb_error(local_ctx, ldb_ret,
ldb_errstring(samdb), &errstr);
}
}
+reply:
compare = &compare_r->msg->r.CompareResponse;
compare->dn = NULL;
compare->resultcode = result;
answers: ARRAY(0)
nsrecs: ARRAY(1)
nsrecs: struct dns_res_rec
+ start_ndr_offset : 0x00000027 (39)
name : 'cnamedotprefix0.samba2003.example.com'
rr_type : DNS_QTYPE_CNAME (0x5)
rr_class : DNS_QCLASS_IN (0x1)